package org.peoples;

import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.util.Date;
import java.util.Iterator;
import java.util.logging.Logger;

import org.peoples.clients.ClientPool;
import org.peoples.clients.IUserHandler;
import org.peoples.clients.User;
import org.peoples.messages.IMessageHandler;
import org.peoples.messages.Message;
import org.peoples.rooms.IRoomHandler;
import org.peoples.rooms.Room;
import org.peoples.rooms.RoomPool;
import org.peoples.sharing.IHello;
import org.red5.server.adapter.ApplicationAdapter;
import org.red5.server.api.IClient;
import org.red5.server.api.IConnection;
import org.red5.server.api.IScope;
import org.red5.server.api.Red5;
import org.red5.server.api.service.IServiceCapableConnection;
import org.red5.server.api.stream.IBroadcastStream;
import org.red5.server.api.stream.IPlayItem;
import org.red5.server.api.stream.IPlaylistSubscriberStream;
import org.red5.server.api.stream.IStreamAwareScopeHandler;
import org.red5.server.api.stream.ISubscriberStream;

public class Application extends ApplicationAdapter implements IStreamAwareScopeHandler, IRoomHandler, 
	IUserHandler, IMessageHandler, IHello {
	
	public static Logger logger = Logger.getLogger(Application.class.getName());
	
	private ClientPool clients;
	private RoomPool rooms;
	
	
	public Application() {
		
		this.clients = ClientPool.getInstance();
		this.rooms = RoomPool.getInstance();
		
		try {
			IHello stub = (IHello) UnicastRemoteObject.exportObject(this, 0);
			
			Registry registry = LocateRegistry.getRegistry();
			
			try {
				registry.bind("IHello", stub);
			} catch (AlreadyBoundException abe) {
				System.err.println("Server exception: " + abe.toString());
				abe.printStackTrace();
			}
		} catch (RemoteException e) {
			System.err.println("Server exception: " + e.toString());
			e.printStackTrace();
		}
		
		logger.info("Peoples Application Started");
		
	}
	
	public String sayHello(String message, Date time) {
		return time.toString() + ": " + message;
	}
	
	public void roomLeave(IClient client, IScope room) {
		super.roomLeave(client, room);
		
		if (room.equals(getUserScope()))
			userLeft(Red5.getConnectionLocal());
	}
	
	
	// ----
	// Room Actions
	// ----
	private IScope getRoomScope() {
		return scope.getScope("rooms");
	}
	
	public void createRoom(String name, String description, Date creationDate) {
		IConnection localConn = Red5.getConnectionLocal();
		logger.info("Client " + localConn.getRemoteAddress() + ": has created a room");
		
		Room room = rooms.addRoom(name, description, creationDate);
		logger.fine("\tRoom: " + room.toString());
		
		if (room != null) {
			Iterator<IConnection> connections = getRoomScope().getConnections();
			
			while (connections.hasNext()) {
				IServiceCapableConnection service = (IServiceCapableConnection) connections.next();
				
				logger.info("Invoking createRoom() on client " + service.getRemoteAddress());
				
				service.invoke("createRoom", new Object[] { room.getRoomProperties() });
			}
		}
		
		System.out.println("");
	}
	
	public void getRooms() {
		IConnection localConn = Red5.getConnectionLocal();
		logger.info("Client " + localConn.getRemoteAddress() + ": has requested for all rooms");
		
		IServiceCapableConnection service = (IServiceCapableConnection) localConn;
		for (Room room : rooms.getAllRooms()) {
			logger.info("Invoking createRoom() on client " + service.getRemoteAddress());
			
			service.invoke("createRoom", new Object[] { room.getRoomProperties() } );
		}
	}
	
	public void getRoomUsers(int roomID) {
		Room room = rooms.getRoom(roomID);
		
		IConnection localConn = Red5.getConnectionLocal();
		logger.info("Client " + localConn.getRemoteAddress() + ": has requested all users for room " + room.toString());
		
		IServiceCapableConnection service = (IServiceCapableConnection) localConn;
		for (IClient client : room.getClients()) {
			logger.info("Invoking addRoomUser() on client " + service.getRemoteAddress());
			
			service.invoke("addRoomUser", new Object[] { room.getRoomProperties(), clients.getUser(client) } );
		}
	}
	
	public void joinRoom(int roomID, int userID) {
		Room room = rooms.getRoom(roomID);
		User user = clients.getUser(userID);
		IClient client = clients.getClient(user);
		
		logger.info("User " + user.toString() + " has joined room " + room.toString());
		
		room.addClient(client);
		
		Iterator<IConnection> connections = room.getConnections().iterator();
		while (connections.hasNext()) {
			IServiceCapableConnection service = (IServiceCapableConnection) connections.next();
			
			logger.info("Invoking addRoomUser() on client " + service.getRemoteAddress());
			
			service.invoke("addRoomUser", new Object[] { room.getRoomProperties(), user } );
		}
		
		System.out.println("");
	}
	
	public void leaveRoom(int roomID, int userID) {
		Room room = rooms.getRoom(roomID);
		User user = clients.getUser(userID);
		IClient client = clients.getClient(user);
		
		logger.info("User " + user.toString() + " has left room " + room.toString());
		
		Iterator<IConnection> connections = room.getConnections().iterator();
		while (connections.hasNext()) {
			IServiceCapableConnection service = (IServiceCapableConnection) connections.next();
			
			logger.info("Invoking removeRoomUser() on client " + service.getRemoteAddress());
			
			service.invoke("removeRoomUser", new Object[] { room.getRoomProperties(), user } );
		}
		
		room.removeClient(client);
		
		System.out.println("");
	}
	
	public void removeRoom(int roomID) {
		Room room = rooms.getRoom(roomID);
		
		IConnection localConn = Red5.getConnectionLocal();
		logger.info("Client " + localConn.getRemoteAddress() + " has removed room " + room.toString());
		
		Iterator<IConnection> connections = getRoomScope().getConnections();
		while (connections.hasNext()) {
			IServiceCapableConnection service = (IServiceCapableConnection) connections.next();
			
			logger.info("Invoking removeRoom() on client " + service.getRemoteAddress());
			
			service.invoke("removeRoom", new Object[] { room.getRoomProperties() });
		}
		
		System.out.println("");
		
		rooms.removeRoom(roomID);
	}
	
	
	// ----
	// User Actions
	// ----
	private IScope getUserScope() {
		return scope.getScope("users");
	}
	
	public void getUsers() {
		IConnection localConn = Red5.getConnectionLocal();
		logger.info("Client " + localConn.getRemoteAddress() + " has requested all connected users");
		
		IServiceCapableConnection service = (IServiceCapableConnection) localConn;
		for (User user : clients.getAllUsers()) {
			logger.info("Invoking addUser() on client " + service.getRemoteAddress());
			
			service.invoke("addUser", new Object[] { user } );
		}
	}
	
	public void userJoin(String firstName, String lastName, Date time) {
		IClient client = Red5.getConnectionLocal().getClient();
		
		User user = clients.addClient(firstName, lastName, time, client);
		
		logger.info("User " + user.toString() + " has joined");
		
		Iterator<IConnection> connections = getUserScope().getConnections();
		while (connections.hasNext()) {
			IServiceCapableConnection service = (IServiceCapableConnection) connections.next();
			
			logger.info("Invoking addUser() on client " + service.getRemoteAddress());
			
			service.invoke("addUser", new Object[] { user });
		}
		
		System.out.println("");
	}
	
	public void userLeft(IConnection conn) {
		System.out.println("[APPLICATION] userLeft()");
		
		IClient client = conn.getClient();
		User user = clients.getUser(client);
		
		logger.info("User " + user.toString() + " has left");
		
		// Only remove the user if they were logged in.
		if (user != null) {
			IConnection current = Red5.getConnectionLocal();
			Iterator<IConnection> connections = getUserScope().getConnections();
			
			
			while (connections.hasNext()) {
				IServiceCapableConnection service = (IServiceCapableConnection) connections.next();
				
				if (current != service) {
					logger.info("Invoking removeUser() on client " + service.getRemoteAddress());
					
					service.invoke("removeUser", new Object[] { user });
				}
			}
			
			System.out.println("");
			
			
			rooms.removeUser(user);
			clients.removeClient(user);
		}
	}
	
	
	// ----
	// Message Actions
	// ----
	public void sendMessage(String message, Date time, int userID, int roomID) {
		Room room = rooms.getRoom(roomID);
		
		Message newMessage = new Message();
		newMessage.message = message;
		newMessage.time = time;
		newMessage.user = clients.getUser(userID);
		newMessage.room = rooms.getRoom(roomID).getRoomProperties();
		
		logger.info("User " + newMessage.user.toString() + " has sent a message in room " + room.toString());
		logger.fine("\tMessage: " + message);
		
		Iterator<IConnection> connections = room.getConnections().iterator();
		while (connections.hasNext()) {
			IServiceCapableConnection service = (IServiceCapableConnection) connections.next();
			
			logger.info("Invoking receiveMessage() on client " + service.getRemoteAddress());
			
			service.invoke("receiveMessage", new Object[] { newMessage });
		}
		
		System.out.println("");
	}
	
	public User testUser(User user) {
		return user;
	}
	
	
	// ----
	// Stream Actions
	// ----
	public void streamPublishStart(IBroadcastStream stream) {
		IConnection localConn = Red5.getConnectionLocal();
		logger.info("Client " + localConn.getRemoteAddress() + " has started a broadcast stream");
	}

	public void streamBroadcastClose(IBroadcastStream stream) {
		// do nothing
	}

	public void streamBroadcastStart(IBroadcastStream stream) {
		// do nothing
	}

	public void streamPlaylistItemPlay(IPlaylistSubscriberStream stream, IPlayItem item, boolean isLive) {
		// do nothing
	}

	public void streamPlaylistItemStop(IPlaylistSubscriberStream stream, IPlayItem item) {
		// do nothing
	}

	public void streamPlaylistVODItemPause(IPlaylistSubscriberStream stream, IPlayItem item, int position) {
		// do nothing
	}

	public void streamPlaylistVODItemResume(IPlaylistSubscriberStream stream, IPlayItem item, int position) {
		// do nothing
	}

	public void streamPlaylistVODItemSeek(IPlaylistSubscriberStream stream, IPlayItem item, int position) {
		// do nothing
	}

	public void streamSubscriberClose(ISubscriberStream stream) {
		// do nothing
	}

	public void streamSubscriberStart(ISubscriberStream stream) {
		// do nothing
	}
	
}
